﻿using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using log4net;

namespace MyLib.Data {
	/// <summary>
	/// 
	/// </summary>
	public class CodeProvider : FileContextProvider {
		/// <summary>
		/// 
		/// </summary>
		private static ILog m_logger = LogManager.GetLogger(typeof(CodeProvider));

		/// <summary>
		/// 
		/// </summary>
		private const string XSD_FILE_NAME = @"Code.xsd";

		/*
		 * Constants of TABLE.
		 */

		/// <summary>
		/// 
		/// </summary>
		private const int TABLE_CATEGORY_ID_COLUMN_INDEX = 0;

		/// <summary>
		/// 
		/// </summary>
		private const int TABLE_VALUE_COLUMN_INDEX = 1;

		/// <summary>
		/// 
		/// </summary>
		private const int TABLE_TEXT_COLUMN_INDEX = 2;

		/// <summary>
		/// 
		/// </summary>
		private const int TABLE_PARENT_VALUE_COLUMN_INDEX = 3;

		/// <summary>
		/// 
		/// </summary>
		private const int TABLE_COLUMNS_COUNT = 4;

		/*
		 * Constants of XML.
		 * 
		 */

		/// <summary>
		/// 
		/// </summary>
		private const string XML_CATEGORIES_NAME = "categories";

		/// <summary>
		/// 
		/// </summary>
		private const string XML_CATEGORY_NAME = "category";

		/// <summary>
		/// 
		/// </summary>
		private const string XML_CATEGORY_ID_NAME = "id";

		/// <summary>
		/// 
		/// </summary>
		private const string XML_CODE_NAME = "code";

		/// <summary>
		/// 
		/// </summary>
		private const string XML_CODE_TEXT_NAME = "text";

		/// <summary>
		/// 
		/// </summary>
		private const string XML_CODE_VALUE_NAME = "value";

		/// <summary>
		/// 
		/// </summary>
		private const string XML_CODE_PARENT_NAME = "parent";

		/*
		 * Private members.
		 */

		/// <summary>
		/// 
		/// </summary>
		private string m_codeFileName;

		/// <summary>
		/// 
		/// </summary>
		private string m_cacheKey;

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public DataTable LoadDataTable() {
			DataTable dataTable = null;

			if (CacheProvider != null) {
				dataTable = CacheProvider[m_cacheKey] as DataTable;
			}

			if (dataTable == null) {
				dataTable = LoadDataTableFromFile(m_codeFileName);

				if (CacheProvider != null) {
					CacheProvider.Add(m_cacheKey, dataTable, m_codeFileName);
				}
			}

			return dataTable;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="dataTable"></param>
		public void SaveDataTable(DataTable dataTable) {
			System.Diagnostics.Debug.Assert(dataTable != null);

			SaveDataTableToFile(m_codeFileName, dataTable);

			if (CacheProvider != null) {
				CacheProvider.Add(m_cacheKey, dataTable, m_codeFileName);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="categoryId"></param>
		/// <param name="value"></param>
		/// <returns></returns>
		public string GetTextFromValue(int categoryId, object value) {
			var exp = string.Format("cat_id = {0} and [value] = '{1}'", categoryId, value);
			var rows = LoadDataTable().Select(exp);

			if (rows != null && rows.Length != 0) {
				return rows[0].GetString(TABLE_TEXT_COLUMN_INDEX);
			} else {
				return string.Empty;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="categoryId"></param>
		/// <returns></returns>
		public IEnumerable<Tuple<string, string, string>> GetCodesFromCategory(int categoryId) {
			var exp = string.Format("cat_id = {0}", categoryId);
			return LoadDataTable().Select(exp)
				.Select(_ =>
					new Tuple<string, string, string>(
						_.GetString(TABLE_VALUE_COLUMN_INDEX),
						_.GetString(TABLE_TEXT_COLUMN_INDEX),
						_.GetString(TABLE_PARENT_VALUE_COLUMN_INDEX)
						));
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="codeFileName"></param>
		/// <returns></returns>
		private static DataTable LoadDataTableFromFile(string codeFileName) {
			m_logger.DebugFormat("Loading datatable from file {0}.", codeFileName);

			// Prepare schema.
			var schemas = new XmlSchemaSet();
			var xss = typeof(CodeProvider)
				.Assembly
				.GetManifestResourceStream("MyLib." + XSD_FILE_NAME); // Use internal schema.
			using (var xsr = XmlReader.Create(xss)) {
				schemas.Add(null, xsr);
			}

			var dt = CreateDataTable();
			if (!System.IO.File.Exists(codeFileName)) {
				return dt;
			}

			// Load code file.
			XElement root;
			using (var codeFileStream = new FileStream(
				codeFileName,
				FileMode.Open,
				FileAccess.Read)) {

				root = XmlExtensions.LoadXElement(codeFileStream, schemas);

				codeFileStream.Close();
			}

			foreach (var catElement in root.Elements(XML_CATEGORY_NAME)) {
				var catId = Convert.ToInt32(catElement.Attribute(XML_CATEGORY_ID_NAME).Value);
				foreach (var codeElement in catElement.Elements(XML_CODE_NAME)) {
					var row = dt.NewRow();

					row[TABLE_CATEGORY_ID_COLUMN_INDEX] = catId;
					row[TABLE_VALUE_COLUMN_INDEX] = codeElement.Attribute(XML_CODE_VALUE_NAME).Value;
					row[TABLE_TEXT_COLUMN_INDEX] = codeElement.Attribute(XML_CODE_TEXT_NAME).Value;
					var pvAttr = codeElement.Attribute(XML_CODE_PARENT_NAME);
					if (pvAttr != null) {
						row[TABLE_PARENT_VALUE_COLUMN_INDEX] = pvAttr.Value;
					} else {
						row[TABLE_PARENT_VALUE_COLUMN_INDEX] = DBNull.Value;
					}

					dt.Rows.Add(row);
				}
			}

			dt.AcceptChanges();

			return dt;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		private static DataTable CreateDataTable() {
			var categoryIdColumn = new DataColumn("cat_id", typeof(int));
			var valueColumn = new DataColumn("value", typeof(string));
			var textColumn = new DataColumn("text", typeof(string));
			var parentValueColumn = new DataColumn("parent_value", typeof(string));

			categoryIdColumn.AllowDBNull = false;
			categoryIdColumn.ReadOnly = true;

			valueColumn.AllowDBNull = false;
			valueColumn.ReadOnly = true;

			textColumn.AllowDBNull = false;
			textColumn.DefaultValue = "";
			textColumn.ReadOnly = true;

			parentValueColumn.AllowDBNull = true;
			parentValueColumn.DefaultValue = null;
			parentValueColumn.ReadOnly = true;

			var columns = new DataColumn[TABLE_COLUMNS_COUNT];
			columns[TABLE_CATEGORY_ID_COLUMN_INDEX] = categoryIdColumn;
			columns[TABLE_VALUE_COLUMN_INDEX] = valueColumn;
			columns[TABLE_TEXT_COLUMN_INDEX] = textColumn;
			columns[TABLE_PARENT_VALUE_COLUMN_INDEX] = parentValueColumn;

			var dt = new DataTable();

			dt.BeginInit();
			dt.Columns.AddRange(columns);
			dt.EndInit();

			return dt;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="codeFileName"></param>
		/// <param name="dataTable"></param>
		private static void SaveDataTableToFile(string codeFileName, DataTable dataTable) {
			using (var stream = new FileStream(codeFileName, FileMode.Create, FileAccess.Write)) {
				var writer = new XmlTextWriter(stream, Encoding.UTF8);
				writer.Indentation = 4;
				writer.IndentChar = ' ';
				writer.Formatting = Formatting.Indented;
				writer.WriteStartDocument(false);

				var categoryIdArray = dataTable.Select(_ => _.GetInt32(TABLE_CATEGORY_ID_COLUMN_INDEX)) // cat_id;
					.Distinct()
					.ToArray();

				writer.WriteStartElement(XML_CATEGORIES_NAME);
				writer.WriteAttributeString("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
				writer.WriteAttributeString("xsi", "noNamespaceSchemaLocation", XmlSchema.InstanceNamespace, XSD_FILE_NAME);

				foreach (var categoryId in categoryIdArray) {
					var exp = string.Format("cat_id = {0}", categoryId);
					var codeRows = dataTable.Select(exp);

					WriteCategory(writer, categoryId, codeRows);
				} // end of foreach.

				writer.WriteEndElement();

				writer.WriteEndDocument();

				writer.Flush();
				writer.Close();
			} // end of using.
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="writer"></param>
		/// <param name="categoryId"></param>
		/// <param name="codeRows"></param>
		private static void WriteCategory(XmlWriter writer, int categoryId, System.Data.DataRow[] codeRows) {
			writer.WriteStartElement(XML_CATEGORY_NAME);
			writer.WriteAttributeString(XML_CATEGORY_ID_NAME, categoryId.ToString());

			foreach (var row in codeRows) {
				writer.WriteStartElement(XML_CODE_NAME);

				writer.WriteAttributeString(XML_CODE_VALUE_NAME, row.GetString(TABLE_VALUE_COLUMN_INDEX));
				writer.WriteAttributeString(XML_CODE_TEXT_NAME, row.GetString(TABLE_TEXT_COLUMN_INDEX));

				if (!row.IsNull(TABLE_PARENT_VALUE_COLUMN_INDEX)) {
					var parent = row.GetString(TABLE_PARENT_VALUE_COLUMN_INDEX); // parentKey.
					if (!string.IsNullOrWhiteSpace(parent)) {
						writer.WriteAttributeString(XML_CODE_PARENT_NAME, parent);
					}
				}

				writer.WriteEndElement();
			}
			writer.WriteEndElement();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="codeFileName"></param>
		public CodeProvider(string codeFileName)
			: this(codeFileName, null) {
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="codeFileName"></param>
		/// <param name="cacheProvider"></param>
		public CodeProvider(string codeFileName, ICacheProvider cacheProvider)
			: base(cacheProvider) {
			System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(codeFileName));

			codeFileName = codeFileName.Trim();

			m_codeFileName = codeFileName;
			m_cacheKey = GetCacheKeyByFileName(
				System.IO.Path.GetFileName(codeFileName));
		}
	} // end of DataTableCache.
}
